#include <glib/g3/tetramesh.h>
#include <glib/g3/io.h>

#include <map>
#include <sstream>

using namespace std;

namespace
{

struct counter {
	int value;
	counter() : value(0) { }
	operator int () const
		{ return value; }
	counter & operator += (int inc)
		{ value += inc; return * this;	}
	counter & operator ++ ()
		{ ++value; return * this; }
};

}

namespace g3
{

bool compute_surface(const tetramesh & mesh, std::vector<face> & surface, glib::progress_callback * pcb)
{
	if (pcb != 0 && !pcb->on_progress(0.0))
		return false;
	
	std::vector<g3::face> tmp;

	typedef map<g3::face, counter> face_map;
	
	face_map faces;

	{
		int percent = -1;
		int done = 0;
		
		// Get tetra faces
		for (size_t i = 0; i < mesh.volume.size(); ++i) 
		{
			const g3::tetra & t = mesh.volume[i];
			int orient;
			g3::face face;
			face = orient_face(t.v[0], t.v[2], t.v[1], orient);
			faces[face] += orient;
			face = orient_face(t.v[0], t.v[1], t.v[3], orient);
			faces[face] += orient;
			face = orient_face(t.v[1], t.v[2], t.v[3], orient);
			faces[face] += orient;
			face = orient_face(t.v[2], t.v[0], t.v[3], orient);
			faces[face] += orient;
			if (pcb != 0)
			{
				int p = int(100 * double(++done) / mesh.volume.size());
				if (p != percent) 
				{
					percent = p;
					if (!pcb->on_progress(float(percent) * 0.01f))
						return false;
				}
			}
		}
	}

	for (face_map::const_iterator it = faces.begin(); it != faces.end(); ++it)
	{
		if (it->second == 1)
			tmp.push_back(it->first);
		else
		if (it->second == -1)
		{
			const face & f = it->first;
			tmp.push_back(face(f.v[0], f.v[2], f.v[1]));
		}
		else
		if (it->second != 0)
		{
			std::ostringstream out;
			out << "Invalid face: " << it->first;
			throw std::runtime_error(out.str());
		}
	}

	surface.swap(tmp);
	return true;
}

void surface_to_trimesh(const tetramesh & tetra, const std::vector<face> & faces, trimesh & tri, std::vector<int> * pindices)
{
	// maps tetra indices to tri indices
	std::vector<int> _indices(tetra.coords.size(), -1);

	tri.coords.clear();
	tri.coords.reserve(faces.size() / 2);

	if (pindices != 0)
	{
		pindices->clear();
		pindices->reserve(faces.size() / 2);
	}

	tri.faces.clear();
	tri.faces.reserve(faces.size());

	for (int i = 0; i < int(faces.size()); ++i)
	{
		const face & f = faces[i];
		face f1;
		for (int j = 0; j < 3; ++j)
		{
			int & index = _indices.at(f.v[j]);
			if (index == -1)
			{
				index = int(tri.coords.size());
				tri.coords.push_back(tetra.coords.at(f.v[j]));
				if (pindices != 0)
					pindices->push_back(f.v[j]);
			}
			f1.v[j] = index;
		}
		tri.faces.push_back(f1);
	}
}

bool compute_surface(const tetramesh & tetra, trimesh & tri, std::vector<int> * pindices, glib::progress_callback * pcb)
{
	std::vector<face> faces;
	if (!compute_surface(tetra, faces, pcb))
		return false;
	surface_to_trimesh(tetra, faces, tri, pindices);
	return true;
}

}

